{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "## Garbage Collection"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import ctypes\n",
    "import gc"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We use the same function that we used in the lesson on reference counting to calculate the number of references to a specified object (using its memory address to avoid creating an extra reference)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def ref_count(address):\n",
    "    return ctypes.c_long.from_address(address).value"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We create a function that will search the objects in the GC for a specified id and tell us if the object was found or not:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def object_by_id(object_id):\n",
    "    for obj in gc.get_objects():\n",
    "        if id(obj) == object_id:\n",
    "            return \"Object exists\"\n",
    "    return \"Not found\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next we define two classes that we will use to create a circular reference\n",
    "\n",
    "Class A's constructor will create an instance of class B and pass itself to class B's constructor that will then store that reference in some instance variable."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class A:\n",
    "    def __init__(self):\n",
    "        self.b = B(self)\n",
    "        print('A: self: {0}, b:{1}'.format(hex(id(self)), hex(id(self.b))))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class B:\n",
    "    def __init__(self, a):\n",
    "        self.a = a\n",
    "        print('B: self: {0}, a: {1}'.format(hex(id(self)), hex(id(self.a))))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We turn off the GC so we can see how reference counts are affected when the GC does not run and when it does (by running it manually)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "gc.disable()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we create an instance of A, which will, in turn, create an instance of B which will store a reference to the calling A instance."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "B: self: 0x1fc1eae44e0, a: 0x1fc1eae4908\n",
      "A: self: 0x1fc1eae4908, b:0x1fc1eae44e0\n"
     ]
    }
   ],
   "source": [
    "my_var = A()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As we can see A and B's constructors ran, and we also see from the memory addresses that we have a circular reference.\n",
    "\n",
    "In fact `my_var` is also a reference to the same A instance:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0x1fc1eae4908\n"
     ]
    }
   ],
   "source": [
    "print(hex(id(my_var)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Another way to see this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a: \t0x1fc1eae4908\n",
      "a.b: \t0x1fc1eae44e0\n",
      "b.a: \t0x1fc1eae4908\n"
     ]
    }
   ],
   "source": [
    "print('a: \\t{0}'.format(hex(id(my_var))))\n",
    "print('a.b: \\t{0}'.format(hex(id(my_var.b))))\n",
    "print('b.a: \\t{0}'.format(hex(id(my_var.b.a))))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "a_id = id(my_var)\n",
    "b_id = id(my_var.b)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can see how many references we have for `a` and `b`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "refcount(a) = 2\n",
      "refcount(b) = 1\n",
      "a: Object exists\n",
      "b: Object exists\n"
     ]
    }
   ],
   "source": [
    "print('refcount(a) = {0}'.format(ref_count(a_id)))\n",
    "print('refcount(b) = {0}'.format(ref_count(b_id)))\n",
    "print('a: {0}'.format(object_by_id(a_id)))\n",
    "print('b: {0}'.format(object_by_id(b_id)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As we can see the A instance has two references (one from `my_var`, the other from the instance variable `b` in the B instance)\n",
    "\n",
    "The B instance has one reference (from the A instance variable `a`)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, let's remove the reference to the A instance that is being held by `my_var`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "my_var= None"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "refcount(a) = 1\n",
      "refcount(b) = 1\n",
      "a: Object exists\n",
      "b: Object exists\n"
     ]
    }
   ],
   "source": [
    "print('refcount(a) = {0}'.format(ref_count(a_id)))\n",
    "print('refcount(b) = {0}'.format(ref_count(b_id)))\n",
    "print('a: {0}'.format(object_by_id(a_id)))\n",
    "print('b: {0}'.format(object_by_id(b_id)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As we can see, the reference counts are now both equal to 1 (a pure circular reference), and reference counting alone did not destroy the A and B instances - they're still around. If no garbage collection is performed this would result in a memory leak."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's run the GC manually and re-check whether the objects still exist:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "refcount(a) = 0\n",
      "refcount(b) = 0\n",
      "a: Not found\n",
      "b: Not found\n"
     ]
    }
   ],
   "source": [
    "gc.collect()\n",
    "print('refcount(a) = {0}'.format(ref_count(a_id)))\n",
    "print('refcount(b) = {0}'.format(ref_count(b_id)))\n",
    "print('a: {0}'.format(object_by_id(a_id)))\n",
    "print('b: {0}'.format(object_by_id(b_id)))"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
